Android系统已经提供了对视音频的强大支持,这边先介绍几个和视音频相关的类,通过这几个类的组合使用,可以实现很多音视频处理的相关功能,下面就对这几个类进行简单介绍。
1 2 3 4 5 6 7 8 MediaMetadataRetriever::用来获取视频的相关信息,例如视频宽高、时长、旋转角度、码率等等。 MediaExtractor::视音频分离器,将一些格式的视频分离出视频轨道和音频轨道。 MediaCodec:视音频相应的编解码类。 MediaMuxer:视音频合成器,将视频和音频合成相应的格式。 MediaFormat:视音频相应的格式信息。 MediaCodec.BufferInfo:存放ByteBuffer相应信息的类。 MediaCrypto:视音频加密解密处理的类。 MediaCodecInfo:视音频编解码相关信息的类。
MediaMetadataRetriever用来获取视音频的相关信息。
使用示例:
1 2 3 4 5 MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); metadataRetriever.setDataSource(file.getAbsolutePath()); String widthString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); String heightString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); String durationString = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
MediaExtractor的 作用是把音频和视频的数据进行分离。
主要API介绍:
setDataSource(String path):设置数据源,即可以设置本地文件又可以设置网络文件
getTrackCount():得到源文件通道数
getTrackFormat(int index):获取指定(index)的通道格式
getSampleTime():返回当前的时间戳
readSampleData(ByteBuffer byteBuf, int offset):把指定通道中的数据按偏移量读取到ByteBuffer中;
advance():读取下一帧数据
release(): 读取结束后释放资源
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(...); int numTracks = extractor.getTrackCount();for (int i = 0 ; i < numTracks; ++i) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (weAreInterestedInThisTrack) { extractor.selectTrack(i); } } ByteBuffer inputBuffer = ByteBuffer.allocate(...) while (extractor.readSampleData(inputBuffer, 0 ) >= 0 ) {int trackIndex = extractor.getSampleTrackIndex();long presentationTimeUs = extractor.getSampleTime(); ... extractor.advance(); } extractor.release(); extractor = null ;
MediaCodec是Android视音频里面最为重要的类,它主要实现的功能是对视音频进行编解码处理。
检查
在Android系统中,MediaCodec支持的格式有限,在使用MediaCodec之前需要对硬编类型的支持进行检测,如果MediaCodec支持再进行使用。
检查使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static MediaCodecInfo selectCodec (String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0 ; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); if (!codecInfo.isEncoder()) { continue ; } String[] types = codecInfo.getSupportedTypes(); for (int j = 0 ; j < types.length; j++) { if (types[j].equalsIgnoreCase(mimeType)) { return codecInfo; } } } return null ; }
一般来说Android MediaCodec支持如下几种颜色格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 private static boolean isRecognizedFormat (int colorFormat) { switch (colorFormat) { case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: return true ; default : return false ; } }
另外MediaCodec支持Surface的方式输入和输出,当编码的时候只需要在Surface上进行绘制就可以输入到编码器,而解码的时候可以将解码图像直接输出到Surface上。
创建
据视音频的类型创建相应的MediaCodec,比如视频使用了H264,而音频使用了AAC-LC,那么创建音频编码器需要传入相应的MIME,AAC-LC对应的是audio/mp4a-latm,而H264对应的是video/avc。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static MediaCodec getAudioMediaCodec () throws IOException { int size = AudioRecord.getMinBufferSize(44100 , AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm" , 44100 , 1 ); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1000 ); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100 ); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, size); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1 ); MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm" ); mediaCodec.configure(format, null , null , MediaCodec.CONFIGURE_FLAG_ENCODE); return mediaCodec; } public static MediaCodec getVideoMediaCodec () throws IOException { int videoWidth = getVideoSize(1280 ); int videoHeight = getVideoSize(720 ); MediaFormat format = MediaFormat.createVideoFormat("video/avc" , videoWidth, videoHeight); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, 1300 * 1000 ); format.setInteger(MediaFormat.KEY_FRAME_RATE, 15 ); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1 ); format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); format.setInteger(MediaFormat.KEY_COMPLEXITY,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); MediaCodec mediaCodec = MediaCodec.createEncoderByType("video/avc" ); mediaCodec.configure(format, null , null , MediaCodec.CONFIGURE_FLAG_ENCODE); return mediaCodec; }
使用
MediaCodec创建之后,需要通过start()方法进行开启。MediaCodec有输入缓冲区队列和输出缓冲区队列,不断通过往输入缓冲区队列传递数据,经过MediaCodec处理后就可以得到响应的输出数据。
当在编码的时候,需要向输入缓冲区传入采集到的原始的视音频数据,然后获取输出缓冲区的数据,输出出来的数据也就是编码处理后的数据。
当在解码的时候,往输入缓冲区输入需要解码的数据,然后获取输出缓冲区的数据,输出出来的数据也就是解码后得到的原始的视音频数据。
当需要清空输入和输出缓冲区的时候,可以调用MediaCodec的flush()方法。当编码或者解码结束时,通过往输入缓冲区输入带结束标记的数据,然后从输出缓冲区可以得到这个结束标记,从而完成整个编解码过程。
在API>21之后,支持同步和异步两种方式
异步使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 MediaCodec codec = MediaCodec.createByCodecName(name); MediaFormat mOutputFormat; codec.setCallback(new MediaCodec.Callback() { @Override void onInputBufferAvailable (MediaCodec mc, int inputBufferId) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); … codec.queueInputBuffer(inputBufferId, …); } @Override void onOutputBufferAvailable (MediaCodec mc, int outputBufferId, …) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); … codec.releaseOutputBuffer(outputBufferId, …); } @Override void onOutputFormatChanged (MediaCodec mc, MediaFormat format) { mOutputFormat = format; } @Override void onError (…) { … } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); codec.start(); codec.stop(); codec.release();
MediaCodec.start()后Codec 立即进入Running子状态,通过设置的callback中的回调方法onInputBufferAvailable()会自动收到可用(empty)的input buffer,此时可以根据input buffer id调用getInputBuffer(id)得到这个buffer,并将需要的处理的数据写入该buffer中,最后调用queueInputBuffer(id, ...)将该buffer提交给Codec处理;Codec每处理完一帧数据就会将处理结果写入一个空的output buffer,并通过回调函数
onOutputBufferAvailable`来通知Client来读取结果,Client可以根据output bufffer id调用getOutputBuffer(id)获取该buffer并读取结果,完毕后可以调用releaseOutputBuffer(id, …)释放该buffer给Codec再次使用,同时也将解码后的内容输出到Surface。
同步示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, ...); MediaFormat outputFormat = codec.getOutputFormat(); codec.start(); for (;;) { int inputBufferId = codec.dequeueInputBuffer(timeoutUs); if (inputBufferId >= 0 ) { ByteBuffer inputBuffer = codec.getInputBuffer(...); ... codec.queueInputBuffer(inputBufferId, ...); } int outputBufferId = codec.dequeueOutputBuffer(...); if (outputBufferId >= 0 ) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); ... codec.releaseOutputBuffer(outputBufferId, ...); } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { outputFormat = codec.getOutputFormat(); } } codec.stop(); codec.release();
一个无限循环中通过调用dequeueInputBuffer(…)和dequeueOutputBuffer(…)来不断地请求Codec是否有可用的input buffer 或 output buffer。如果有可用的input buffer:根据得到的buffer id,调用getInputBuffer(id)获取该buffer,并向其中写入待处理的数据,然后调用queueInputBuffer(id,…)提交到Codec进行处理。如果有可用的output buffer: 根据得到的buffer id,调用getOutputBuffer(id)获取该buffer,读取其中的处理结果,然后调用releaseOutputBuffer(id,…)释放该buffer供Codec再次使用,并输出到surface。
MediaMuxer的作用是生成音频或视频文件;还可以把音频与视频混合成一个音视频文件。
相关API介绍:
MediaMuxer(String path, int format):path:输出文件的名称 format:输出文件的格式;当前只支持MP4格式;
addTrack(MediaFormat format):添加通道;我们更多的是使用MediaCodec.getOutpurForma()或Extractor.getTrackFormat(int index)来获取MediaFormat;也可以自己创建;
start():开始合成文件
writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer中的数据写入到在构造器设置的文件中;
stop():停止合成文件
release():释放资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 MediaMuxer muxer = new MediaMuxer("temp.mp4" , OutputFormat.MUXER_OUTPUT_MPEG_4); MediaFormat audioFormat = new MediaFormat(...); MediaFormat videoFormat = new MediaFormat(...); int audioTrackIndex = muxer.addTrack(audioFormat);int videoTrackIndex = muxer.addTrack(videoFormat);ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize); boolean finished = false ;BufferInfo bufferInfo = new BufferInfo(); muxer.start(); while (!finished) { int readSampleSize = mediaExtractor.readSampleData(byteBuffer, 0 ); if (readSampleSize < 0 ) { mediaExtractor.unselectTrack(videoIndex); finished = true ; break ; } if (!finished) { int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); mediaExtractor.advance(); } }; muxer.stop(); muxer.release();